package lambdasinaction.chap5.my;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.IntSupplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * TODO
 *
 * @author Fish
 * @since 0.0.1 2018/7/24 10:53
 */
public class MyBuildingStreams {

  public static void main(String[] args) {
    // Stream.of 使用静态方法，通过显示值创建一个流。它接受任意数量的参数。
    // 例如，以下代码直接使用 Stream.of 创建了一个字符串流。然后，你可以将字符串转换为大写，再一个个打印出来
    Stream<String> stream = Stream.of("Java 8", "Lambdas", "In", "Action");
    stream.map(String::toUpperCase).forEach(System.out::println);

    // 你可以使用 Stream.empty 得到一个空流
    Stream<String> emptyStream = Stream.empty();

    // Arrays.stream 使用静态方法，从数组创建一个流。它接受一个数组作为参数。
    // 例如，你可以将一个原始类型int的数组转换成一个 IntStream
    int[] numbers = {2, 3, 5, 7, 11, 13};
    System.out.println(Arrays.stream(numbers).sum());

    // 由函数生成流，创建无限流
    // Stream API提供了两个静态方法从函数生成流：Stream.iterator和 Stream.generate
    // 这两个操作可以创建所谓的无限流：不像从固定集合创建的流那样有固定大小的流。
    // 由iterate 和 generate 创建的流会用给定的函数按需创建值，因此可以无穷无尽地计算下去
    // 一般来说应该使用 limit(n) 来对这种流加以限制，以避免打印无穷多个值

    // Stream.iterate 迭代流
    Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);

    // 斐波纳契元组序列 fibonnaci with iterate
    Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
        .limit(10)
        .forEach(t -> System.out.println("(" + t[0] + "," + t[1] + ")"));
    // 如果你只想打印正常的斐波纳契数列，可以使用 map提取每个元祖中的第一个元素
    Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
        .limit(10)
        .map(t -> t[0])
        .forEach(System.out::println);

    // 与 iterate 方法类似，generate方法也可让你按需生成一个无限流
    // 但 generate 不是依次对每个生成的值应用函数的。它接受一个Supplier<T>类型的 Lambda 提供新的值。
    // 这段代码将生成一个流，其中有五个0到1之间的随机双精度数
    Stream.generate(Math::random).limit(5).forEach(System.out::println);

    // 使用IntStream说明避免装箱操作的代码
    // IntStream的 generate方法会接受一个IntSupplier，而不是Supplier
    // 例如，可以这样来生成一个全是1的无限流
    IntStream.generate(() -> 1).limit(5).forEach(System.out::println);

    IntStream.generate(new IntSupplier() {
      @Override
      public int getAsInt() {
        return 2;
      }
    }).limit(5).forEach(System.out::println);

    /**
     * 前面的代码创建了一个IntSupplier的实例。此对象有可变的状态:它在两个实例变量中
     * 记录了前一个斐波纳契项和当前的斐波纳契项。getAsInt在调用时会改变对象的状态，
     * 由此在 每次调用时产生新的值。相比之下，使用iterate的方法则是纯粹不变的:它没有修改现有状态，
     * 但在每次迭代时会创建新的元组。你将在第7章了解到，你应该始终采用不变的方法，
     * 以便并行 处理流，并保持结果正确。
     * 请注意，因为你处理的是一个无限流，所以必须使用limit操作来显 式限制它的大小;
     * 否则，终端操作(这里是forEach)将永远计算下去。
     * 同样，你不能对无限流 做排序或归约，因为所有元素都需要处理，而这永远也完不成!
     */
    System.out.println("---fib---");
    IntSupplier fib = new IntSupplier() {
      private int previous = 0;
      private int current = 1;
      @Override
      public int getAsInt() {
        int nextValue = this.previous + this.current;
        this.previous = this.current;
        this.current = nextValue;
        return this.previous;
      }
    };
    IntStream.generate(fib).limit(10).forEach(System.out::println);

    /**
     * 由文件生成流
     * Java中用于处理文件等 I/O操作的NIO API(非阻塞 I/O)已更新，以便利用 StreamAPI
     * java.nio.file.Files中的很多静态方法都会返回一个流。
     * 例如，一个很有用的方法是 Files.lines，它会返回一个由指定文件中的各行构成的字符串流
     */

    long uniqueWords = 0;
    // 流会自动关闭
    try (Stream<String> lines = Files.lines(Paths.get("src/main/resources/lambdasinaction/chap5/data.txt"), Charset.defaultCharset())) {
      uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" "))).distinct().count();
    } catch (IOException e) {
      e.printStackTrace();
    }
    System.out.println("There are " + uniqueWords + " unique words in data.txt");
  }

}
